﻿using System;
using System.Collections.Generic;
using System.Configuration;

namespace BizElements.BusinessLayer
{
    #region Documentation.
    /// <summary>
    /// Configurable factory class that returns <see cref="ILockManager"/> singleton objects.
    /// </summary>
    /// <remarks>If not otherwise configured, <b>BizElements.BusinessLayer.DefaultLockManager</b> is used as
    /// a default manager for object types which don't have a dedicated/custom manger registered. This can
    /// be changed in the <b>appSettings</b> section of an application's config file:
    /// <code>
    /// <add key="BizElements.BusinessLayer.LockManagerFactory.DefaultManagerAssembly" value="BizElements.BusinessLayer"/>
    /// <add key="BizElements.BusinessLayer.LockManagerFactory.DefaultManagerClass" value="BizElements.BusinessLayer.DefaultLockManager"/>
    /// </code>
    /// </remarks>
    /// <seealso cref="ILockManager"/><seealso cref="LockUtil"/><seealso cref="LockMode"/>
    #endregion
    public static class LockManagerFactory
    {
        #region Configuration.

        const string defaultManagerAssembly = "BizElements.BusinessLayer";
        const string defaultManagerClass = "BizElements.BusinessLayer.DefaultLockManager";
        static ILockManager defaultManager;
        static Dictionary<string, ILockManager> customManagers = new Dictionary<string, ILockManager>();

        static LockManagerFactory()
        {
            string cfgDefaultManagerAsm = ConfigurationManager.AppSettings["BizElements.BusinessLayer.LockManagerFactory.DefaultManagerAssembly"];

            string cfgDefaultManagerClass = ConfigurationManager.AppSettings["BizElements.BusinessLayer.LockManagerFactory.DefaultManagerClass"];

            defaultManager = ReflectionUtil.TryCreateInstance(cfgDefaultManagerAsm, cfgDefaultManagerClass) as ILockManager;
            if (defaultManager == null)
                defaultManager = ReflectionUtil.TryCreateInstance(defaultManagerAssembly, defaultManagerClass) as ILockManager;

            if (defaultManager == null) throw new InvalidProgramException("Cannot create default ILockManager instance.");
        }

        #endregion

        #region Singleton.

        /// <summary>
        /// Gets default manager used for object types which don't have a dedicated/custom manager registered.
        /// </summary>
        public static ILockManager DefaultManager
        {
            get { return defaultManager; }
        }

        /// <summary>
        /// Gets the manager for the specifed object type.
        /// </summary>
        /// <param name="objectType">Object type. Typically a class or a table name.</param>
        /// <returns>ILockManager object which manages lock for the specified type.</returns>
        public static ILockManager GetManager(string objectType)
        {
            ILockManager manager;
            bool foundCustomManager = customManagers.TryGetValue(objectType, out manager);
            if (!foundCustomManager)
                manager = defaultManager;

            return manager;
        }

        /// <summary>
        /// Registers dedicated <see cref="ILockManager"/> object to use with the specified object type.
        /// </summary>
        /// <param name="objectType">Object type. Typically a class or a table name.</param>
        /// <param name="manager">Custom manager which will handle ofline concurrency for the specified type.</param>
        /// <remarks>Once a custom manager is registered for an object type it cannot be changed or unregistered.
        /// Consecutive RegisterManager method invocations for the same object type are ignored.</remarks>
        public static void RegisterManager(string objectType, ILockManager manager)
        {
            if (string.IsNullOrEmpty(objectType)) throw new ArgumentNullException("objectType", "Object type must be specified.");
            if (manager == null) throw new ArgumentNullException("manager", "Lock manager must be specifed.");
            bool alreadyRegistered = customManagers.ContainsKey(objectType);
            if (!alreadyRegistered)
            {
                lock (customManagers)
                {
                    customManagers.Add(objectType, manager);
                }
            }
        }

        /// <summary>
        /// Checks whether a dedicated/custom <see cref="ILockManager"/> object is registered for use with the specified object type.
        /// </summary>
        /// <param name="objectType">Object type. Typically a class or a table name.</param>
        public static bool HasCustomManager(string objectType)
        {
            return customManagers.ContainsKey(objectType);
        }

        #endregion
    }
}